package com.mybatisflex.admin.spring.log;


import com.alibaba.fastjson2.JSON;
import com.mybatisflex.core.util.ClassUtil;
import com.mybatisflex.core.util.StringUtil;
import org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtClass;
import org.apache.ibatis.javassist.CtMethod;
import org.apache.ibatis.javassist.NotFoundException;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.StringJoiner;

@Aspect
@Component
public class WebLogAspect {

    private static int maxOutputLengthOfParaValue = 512;


    @Pointcut("execution(public * com.mybatisflex.admin.controller..*.*(..))")
    public void webLog() {
    }

    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        Class<?> controllerClass = signature.getDeclaringType();
        Method method = signature.getMethod();

        String url = request.getRequestURL().toString();
        String param = getRequestParamsString(request);

        int lineNumber = getLineNumber(controllerClass, method);


        long startTime = System.currentTimeMillis();
        Object result = null;
        try {
            result = proceedingJoinPoint.proceed();
        } finally {
            StringBuilder logInfo = new StringBuilder();
            logInfo.append("\n");
            logInfo.append("+========================================= Start ==========================================\n");
            logInfo.append("| Request        : ").append(request.getMethod()).append(" ").append(url).append("\n");
            logInfo.append("| Request Params : ").append(param).append("\n");
            logInfo.append("| Request IP     : ").append(request.getRemoteAddr()).append("\n");
            logInfo.append("| Controller     : ").append(signature.getDeclaringTypeName()).append(".").append("(").append(controllerClass.getSimpleName()).append(".java:").append(lineNumber).append(")").append("\n");
            logInfo.append("| Method         : ").append(method.getName()).append(buildParamsString(method)).append("\n");
            logInfo.append("| Response       : ").append(getResponseText(result)).append("\n");
            logInfo.append("| Elapsed Time   : ").append(System.currentTimeMillis() - startTime).append("ms").append("\n");
            logInfo.append("+========================================== End ===========================================\n");
            System.out.println(logInfo);
        }
        return result;
    }

    private static String getResponseText(Object result) {
        if (result instanceof ModelAndView && ((ModelAndView) result).isReference()) {
            return ((ModelAndView) result).getViewName();
        }

        String originalText;
        if (result instanceof String) {
            originalText = (String) result;
        } else {
            originalText = JSON.toJSONString(result);
        }

        if (StringUtil.isBlank(originalText)) {
            return "";
        }

        originalText = originalText.replace("\n", "");

        if (originalText.length() > 100) {
            return originalText.substring(0, 100) + "...";
        }

        return originalText;
    }


    private String buildParamsString(Method method) {
        StringJoiner joiner = new StringJoiner(", ", "(", ")");
        for (Class<?> parameterType : method.getParameterTypes()) {
            joiner.add(parameterType.getSimpleName());
        }
        return joiner.toString();
    }


    private int getLineNumber(Class<?> controllerClass, Method method) throws NotFoundException {
        CtClass ctClass = ClassPool.getDefault().get(ClassUtil.getUsefulClass(controllerClass).getName());
        ClassPool.getDefault().get(ClassUtil.getUsefulClass(controllerClass).getName());
        String desc = WebLogUtil.getMethodDescWithoutName(method);
        CtMethod ctMethod = ctClass.getMethod(method.getName(), desc);
        return ctMethod.getMethodInfo().getLineNumber(0);
    }


    private String getRequestParamsString(HttpServletRequest request) {
        StringBuilder sb = new StringBuilder();
        Enumeration<String> e = request.getParameterNames();
        if (e.hasMoreElements()) {
            while (e.hasMoreElements()) {
                String name = e.nextElement();
                String[] values = request.getParameterValues(name);
                if (values.length == 1) {
                    sb.append(name).append("=");
                    if (values[0] != null && values[0].length() > maxOutputLengthOfParaValue) {
                        sb.append(values[0], 0, maxOutputLengthOfParaValue).append("...");
                    } else {
                        sb.append(values[0]);
                    }
                } else {
                    sb.append(name).append("[]={");
                    for (int i = 0; i < values.length; i++) {
                        if (i > 0) {
                            sb.append(",");
                        }
                        sb.append(values[i]);
                    }
                    sb.append("}");
                }
                sb.append("  ");
            }
        }
        return sb.toString();
    }

}